<?php declare(strict_types=1);
/**
 * @author      xianganyall <xianganyall@gmail.com>
 * @copyright   2023-2025 owner
 **/

namespace Srv\Libs\Plugins\Qc;

use Srv\Conf\ConfModel\SmsConf;
use Srv\Libs\Common\CommJson;
use Srv\Libs\Common\CommNet;
use Srv\Libs\Common\CommTime;
use Srv\Libs\Frame\Logs;

class QcSms
{
    private string $sdkAppId                = '';       // sdkAppId
    private string $signString              = '';       // 签名内容无【】
    private string $secretId                = '';       // secretId
    private string $secretKey               = '';       // secretKey
    private string $logPath                 = '';       // 日志路径

    /**
     * @param SmsConf $SmsConf
     * __construct
     */
    public function __construct(SmsConf $SmsConf)
    {
        $this->sdkAppId         = $SmsConf->getAppId();
        $this->signString       = $SmsConf->getAppSign();
        $this->secretId         = $SmsConf->getSecretId();
        $this->secretKey        = $SmsConf->getSecretKey();
        $this->logPath          = $SmsConf->getLogPath();
    }

    /**
     * @param int $code
     * @param string $mobile
     * @param string $templateId
     * @param array $templateData
     * @return bool
     * send
     */
    public function send(int $code, string $mobile, string $templateId, array $templateData):bool
    {
        $service                = 'sms';
        $region                 = '';
        $algorithm              = 'TC3-HMAC-SHA256';
        $postData               = [];
        $postData['Action']     = 'SendSms';
        $postData['Region']     = '';
        $postData['Version']    = '2019-07-11';
        // 发送参数
        $PhoneNumberSet                     = '+'.$code.$mobile;
        $postData['PhoneNumberSet']         = [$PhoneNumberSet];
        $postData['TemplateID']             = $templateId;
        $postData['SmsSdkAppid']            = $this->sdkAppId;
        $postData['Sign']                   = $this->signString;
        $postData['TemplateParamSet']       = $templateData;
        $body                   = '';
        $resData                = $this->getQcloudData($service, $postData, $algorithm, $region, $this->secretId, $this->secretKey, $body);
        if(!isset($resData['Response']) || !isset($resData['Response']['SendStatusSet']) || count($resData['Response']['SendStatusSet']) < 1){
            if(strlen($this->logPath) > 0) Logs::error(CommJson::encodeArray(['res' => 'FAIL', 'POST' => $postData, 'BODY' => $body]), __LINE__, __FILE__, $this->logPath);
            return false;
        }
        foreach($resData['Response']['SendStatusSet'] as $resVal){
            if(isset($resVal['Code']) && isset($resVal['PhoneNumber']) && $resVal['PhoneNumber'] === $PhoneNumberSet){
                if(strtoupper($resVal['Code']) === 'OK'){
                    if(strlen($this->logPath) > 0) Logs::info(CommJson::encodeArray(['res' => 'OK', 'POST' => $postData, 'BODY' => $body]), __LINE__, __FILE__, $this->logPath);
                    return true;
                }else{
                    break;
                }
            }
        }
        if(strlen($this->logPath) > 0) Logs::error(CommJson::encodeArray(['res' => 'FAIL', 'POST' => $postData, 'BODY' => $body]), __LINE__, __FILE__, $this->logPath);
        return false;
    }

    /**
     * @param string $service
     * @param array $postData
     * @param string $algorithm
     * @param string $region
     * @param string $secretId
     * @param string $secretKey
     * @param string $body
     * @return array
     * 请求Qcloud数据
     */
    private function getQcloudData(string $service, array $postData, string $algorithm, string $region, string $secretId, string $secretKey, string &$body):array
    {
        $resData            = [];
        $hostName           = $service.'.'.(strlen($region) > 0 ? $region.'.' : '').'tencentcloudapi.com';
        $serviceUrl         = 'https://'.$hostName;
        $method             = 'POST';
        $query              = '';
        $currTime           = CommTime::getTimeStamp(0);
        $headerData         = [     // 必须key=>val数组
            'Content-Type'      => 'application/json',
            'Host'              => $hostName,
            'X-TC-Action'       => $postData['Action'],
            'X-TC-Timestamp'    => strval($currTime),
            'X-TC-Version'      => $postData['Version'],
        ];
        if(strlen($postData['Region']) > 0){
            $headerData['X-TC-Region'] = $postData['Region'];
        }
        unset($postData['Action'], $postData['Region'], $postData['Version']);  // 必须移除POST参数
        if(count($postData) < 1) return $resData;
        $postDataString     = CommJson::encodeArray($postData);
        $authorizationSign  = $this->getQcloudSign($currTime, $service, $secretId, $secretKey, $algorithm, $method, $query, $headerData, $postDataString);
        $header             = [
            'Authorization' => $authorizationSign,
        ];
        $header             = array_merge_recursive($header, $headerData);
        $param              = ['TIMEOUT' => 5, 'METHOD' => $method, 'POST_DATA' => $postDataString, 'HEADER' => $header];
        $urlInfo            = CommNet::getUrlInfo($serviceUrl, $param);
        $body               = trim($urlInfo['body'] ?? '');
        if(strlen($body) > 2){
            $resData        = CommJson::decodeArray($body);
        }
        return $resData;
    }

    /**
     * @param int $currTime
     * @param string $service
     * @param string $secretId
     * @param string $secretKey
     * @param string $algorithm
     * @param string $method
     * @param string $query
     * @param array $headerData
     * @param string $postDataString
     * @return string
     * 获取签名
     */
    private function getQcloudSign(int $currTime, string $service, string $secretId, string $secretKey, string $algorithm, string $method, string $query, array $headerData, string $postDataString):string
    {
        // No.1
        $signedHeaders              = '';
        $canonicalRequest           = $this->getCanonicalRequest($method, $query, $headerData, $postDataString, $signedHeaders);
        // No.2
        $requestTc3                 = 'tc3_request';
        $dateString                 = '';
        $credentialScope            = $this->getCredentialScope($currTime, $service, $requestTc3, $dateString);
        $hashedCanonicalRequest     = $this->getHashedSha256String($canonicalRequest);
        $SignatureString            = $this->getSignAuthString($algorithm, $currTime, $credentialScope, $hashedCanonicalRequest);
        // No.3
        $secretSignature            = $this->signTC3($secretKey, $dateString, $service, $SignatureString);
        return $algorithm.' Credential='.$secretId.'/'.$credentialScope.', SignedHeaders='.$signedHeaders.', Signature='.$secretSignature;
    }

    /**
     * @param string $method
     * @param string $query
     * @param array $headerData
     * @param string $postDataString
     * @param string $signedHeaders
     * @return string
     * 获取拼接规范请求串
     */
    private function getCanonicalRequest(string $method, string $query, array $headerData, string $postDataString, string &$signedHeaders):string
    {
        $method                 = strtoupper($method);      // POST GET
        $uri                    = '/';                      // URI 参数，API 3.0 固定为正斜杠
        $canonicalHeaders       = '';
        $signedHeaders          = '';
        if(count($headerData) > 0){
            ksort($headerData);
            foreach($headerData as $key => $val){
                if(is_int($key)) continue;
                $key                = trim(strval($key));
                if(strlen($key) < 1) continue;
                if(strtoupper(substr($key, 0, 5)) === 'X-TC-') continue;
                $val                = trim(strval($val));
                $canonicalHeaders   .= strtolower($key).':'.$val."\n";
                $signedHeaders      .= (strlen($signedHeaders) < 1 ? '' : ';').strtolower($key);
            }
        }
        $hashedRequestPayload = '';
        if($method !== 'GET' && strlen($postDataString) > 0){
            $hashedRequestPayload = hash('sha256', $postDataString, false);     // 小写十六进制字符串
            if(!is_string($hashedRequestPayload)) $hashedRequestPayload = '';
        }
        return $method."\n".$uri."\n".$query."\n".$canonicalHeaders."\n".$signedHeaders."\n".$hashedRequestPayload;
    }

    /**
     * @param int $requestTimestamp
     * @param string $service
     * @param string $requestTc3
     * @param string $dateString
     * @return string
     * 获取凭证范围
     */
    private function getCredentialScope(int $requestTimestamp, string $service, string $requestTc3, string &$dateString):string
    {
        if($requestTimestamp < 1 || strlen($service) < 1) return '';
        $dateString         = strval(gmdate('Y-m-d', $requestTimestamp));
        return $dateString.'/'.$service.'/'.$requestTc3;
    }

    /**
     * @param string $string
     * @return string
     * 计算Hash小写十六进制字符串
     */
    private function getHashedSha256String(string $string):string
    {
        $hashedString = hash('sha256', $string, false);    // 小写十六进制字符串
        if(!is_string($hashedString)) return '';
        return $hashedString;
    }

    /**
     * @param string $secretKey
     * @param string $dateString
     * @param string $service
     * @param string $SignatureString
     * @return string
     * 计算签名TC3
     */
    private function signTC3(string $secretKey, string $dateString, string $service, string $SignatureString):string
    {
        $dateKey        = hash_hmac('SHA256', $dateString, 'TC3'.$secretKey, true);
        $serviceKey     = hash_hmac('SHA256', $service, $dateKey, true);
        $reqKey         = hash_hmac('SHA256', 'tc3_request', $serviceKey, true);
        return hash_hmac('SHA256', $SignatureString, $reqKey, false);
    }

    /**
     * @param string $algorithm
     * @param int $requestTimestamp
     * @param string $credentialScope
     * @param string $hashedCanonicalRequest
     * @return string
     * 获取签名认证
     */
    private function getSignAuthString(string $algorithm, int $requestTimestamp, string $credentialScope, string $hashedCanonicalRequest):string
    {
        return $algorithm."\n".$requestTimestamp."\n".$credentialScope."\n".$hashedCanonicalRequest;
    }
}